package com.vi.vioserial.util;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.serialport.SerialPort;
import android.text.TextUtils;

import com.hzjq.core.util.BlastLog;
import com.vi.vioserial.listener.OnSerialDataListener;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidParameterException;
import java.util.ArrayList;

/**
 * Serial port auxiliary tool class
 *
 * @author Vi
 * @date 2019-07-17 17:43
 * @e-mail cfop_f2l@163.com
 */

public abstract class VioSerialHelper {
    private static String TAG = "VioSerial";
    private static String TAG_END = "0D0A";
    private SerialPort mSerialPort;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;

    private boolean _isOpen = false;
    private long iDelay = 5;
    private long iGap = 5;

    private String sPort;//Serial port
    private int iBaudRate;//baud rate
    private int mStopBits = 1;//StopBits，1 or 2  （default 1）
    private int mDataBits = 8;// DataBits，5 ~ 8  （default 8）
    private int mParity = 0;//Parity，0 None（default）； 1 Odd； 2 Even
    private int mFlowCon = 0;//FlowCon

    private HandlerThread mHandlerThread;
    private Handler mWorkHandler;

    private OnSerialDataListener mSerialDataListener = null;

    private static StringBuffer buffer = new StringBuffer();
    private static int length = 0;
    private static int index = 0;
    private static ArrayList<String> cacheBuffer = new ArrayList<>();
    private static boolean isLoop = false;

    public VioSerialHelper() {

    }

    public VioSerialHelper(String sPort, int iBaudRate) {
        this.sPort = sPort;
        this.iBaudRate = iBaudRate;
    }

    public void setSerialDataListener(OnSerialDataListener serialDataListene) {
        this.mSerialDataListener = serialDataListene;
    }

    public void open() throws SecurityException, IOException, InvalidParameterException {
        mSerialPort = new SerialPort(new File(sPort), iBaudRate, mStopBits, mDataBits, mParity, mFlowCon, 0);
        mOutputStream = mSerialPort.getOutputStream();
        mInputStream = mSerialPort.getInputStream();
        mReadThread = new ReadThread();
        mReadThread.start();
        startLoopBuffer();
        makeWriteThreadAlive();
        _isOpen = true;
    }


    public void close() {
        try {
            if (mInputStream != null) {
                mInputStream.close();
            }
            if (mOutputStream != null) {
                mOutputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;
        }
        if (mHandlerThread != null) {
            mHandlerThread.quit();
            mHandlerThread = null;
        }
        _isOpen = false;
    }

    protected void addWaitMessage(Message msg) {
        //startSendThread(msg);
        mWorkHandler.sendMessage(msg);
    }

    private void send(byte[] bOutArray) {

        makeWriteThreadAlive();

        if (!_isOpen) {
            return;
        }

        try {
            mOutputStream.write(bOutArray);
            makeReadThreadAlive();
            if (mSerialDataListener != null) {
                String hexString = SerialDataUtils.ByteArrToHex(bOutArray).trim();
                mSerialDataListener.onSend(hexString);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private void makeWriteThreadAlive() {
        if (mWorkHandler == null || mHandlerThread.isInterrupted() || !mHandlerThread.isAlive()) {
            mHandlerThread = new HandlerThread("handlerThread");
            mHandlerThread.start();
            mWorkHandler = new Handler(mHandlerThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    byte[] sendData = (byte[]) msg.obj;
                    send(sendData);

                    try {
                        Thread.sleep(iDelay);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }
    }

    private void makeReadThreadAlive() {
        if (mReadThread == null || !mReadThread.isAlive() || mReadThread.isInterrupted()) {
            mReadThread = new ReadThread();
            mReadThread.start();
        }
    }

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                try {
                    if (mInputStream == null) return;
                    try {
                        Thread.sleep(iGap);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    byte[] buffer = new byte[5];
                    int im = mInputStream.available();
                    if (im > 0) {
                        buffer = new byte[im];
                        int size = mInputStream.read(buffer);
                        String hexString = SerialDataUtils.byteToStr(buffer, size).trim();
                        BlastLog.Companion.e("read : " + hexString + "  isLoop:" + isLoop);
                        if (hexString.startsWith("23") && hexString.length() > 8 && hexString.substring(4, 8).equals("A0FF")) {
                            cacheBuffer.add(0, hexString);
                        } else {
                            cacheBuffer.add(hexString);
                        }
                    }
                    startLoopBuffer();
                } catch (Throwable e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }



    private void startLoopBuffer() {
        if (!cacheBuffer.isEmpty() && !isLoop) {
            String remove = cacheBuffer.remove(0);
            if (!TextUtils.isEmpty(remove)) {
                isLoop = true;
                checkPack(remove);
            }
        }
    }


    private void checkPack(String tempData) {
//        BlastLog.Companion.e("checkPack:" + tempData);
        if (tempData.startsWith("23") && tempData.length() > 8 && tempData.substring(4, 8).equals("A0FF")) {
            buffer.delete(0, buffer.length());
            length = Integer.parseInt(tempData.substring(2, 4), 16);
            buffer.append(tempData);
            if (buffer.length() == length * 2) {
                onCallback();
            }
            if(buffer.length() > length*2){
                cacheBuffer.add(buffer.substring(length*2,buffer.length()));
                String cb = buffer.substring(0,length*2);
                buffer.delete(0, buffer.length());
                buffer.append(cb);
                onCallback();
            }
            isLoop = false;
        } else {
//            BlastLog.Companion.e("tempData.length() = " + tempData.length()
//                    + " buffer.length() = " + buffer.length() + " length = " + (length * 2));
            if (buffer.toString().startsWith("23") && buffer.length() > 8 && buffer.substring(4, 8).equals("A0FF")) {
                length = Integer.parseInt(buffer.substring(2, 4), 16);
                buffer.append(tempData);
                if (buffer.length() == length * 2) {
                    onCallback();
                } else if(buffer.length() > length*2){
                    cacheBuffer.add(buffer.substring(length*2,buffer.length()));
                    String cb = buffer.substring(0,length*2);
                    buffer.delete(0, buffer.length());
                    buffer.append(cb);
                    onCallback();
                } else {
                    buffer.delete(0, buffer.length());
                    length = 0;
                }
                isLoop = false;
            } else {
                buffer.delete(0, buffer.length());
                length = 0;
                isLoop = false;
            }

        }

    }


    private void onCallback() {
        if (buffer.toString().startsWith("23") && buffer.length() > 8 && buffer.substring(4, 8).equals("A0FF")) {
            if (length * 2 == buffer.length()) {
                if (mSerialDataListener != null) {
                    mSerialDataListener.onReceiveFullData(buffer.toString());
                }
                onDataReceived(buffer.toString());
                buffer.delete(0, buffer.length());
            }
        }
    }

    public boolean isOpen() {
        return _isOpen;
    }

    protected abstract void onDataReceived(String ComRecData);

    public String getPort() {
        return sPort;
    }

    public void setPort(String sPort) {
        this.sPort = sPort;
    }

    public int getBaudRate() {
        return iBaudRate;
    }

    public void setBaudRate(int iBaudRate) {
        this.iBaudRate = iBaudRate;
    }

    public int getStopBits() {
        return mStopBits;
    }

    public void setStopBits(int mStopBits) {
        this.mStopBits = mStopBits;
    }

    public int getDataBits() {
        return mDataBits;
    }

    public void setDataBits(int mDataBits) {
        this.mDataBits = mDataBits;
    }

    public int getParity() {
        return mParity;
    }

    public void setParity(int mParity) {
        this.mParity = mParity;
    }

    public int getFlowCon() {
        return mFlowCon;
    }

    public void setFlowCon(int mFlowCon) {
        this.mFlowCon = mFlowCon;
    }

    public void setDelay(long delay) {
        iDelay = delay;
    }

    public void setGap(long gap) {
        iGap = gap;
    }

}